package com.leyou.auth.service;

import com.leyou.auth.config.JwtProperties;
import com.leyou.auth.mapper.ApplicationInfoMapper;
import com.leyou.auth.pojo.ApplicationInfo;
import com.leyou.common.auth.entity.AppInfo;
import com.leyou.common.auth.entity.Payload;
import com.leyou.common.auth.entity.UserInfo;
import com.leyou.common.auth.utils.JwtUtils;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exception.LyException;
import com.leyou.common.utils.CookieUtils;
import com.leyou.user.client.UserClient;
import com.leyou.user.dto.UserDTO;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author chao
 */
@Slf4j
@Service
public class AuthService {
    @Autowired
    private JwtProperties properties;
    @Autowired
    private UserClient userClient;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private ApplicationInfoMapper infoMapper;
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    public void login(String username, String password, HttpServletResponse response) {
        try {
            // 根据用户名和密码查询用
            UserDTO userDTO = userClient.queryByUsernameAndPassword(username, password);
            // 生成jwt
            UserInfo userInfo = new UserInfo(userDTO.getId(), userDTO.getUsername(), "guest");
            String token = JwtUtils.generateTokenExpireInMinutes(userInfo, properties.getPrivateKey(),
                    properties.getUser().getExpire());
            // 存入cookie
            CookieUtils.newCookieBuilder().name(properties.getUser().getCookieName())
                    .value(token)
                    .domain(properties.getUser().getCookieDomain())
                    .httpOnly(true)
                    .response(response)
                    .build();
        } catch (Exception e) {
            // 用户名或密码错误
            log.error("用户名或密码错误：{}", e.getMessage());
            throw new LyException(ExceptionEnum.INVALID_USERNAME_PASSWORD);
        }
    }

    public UserInfo verifyUser(HttpServletRequest request, HttpServletResponse response) {
        try {
            // 获取cookie中的token
            String token = CookieUtils.getCookieValue(request, properties.getUser().getCookieName());
            // 解析token
            Payload<UserInfo> payload = JwtUtils.getInfoFromToken(token, properties.getPublicKey(), UserInfo.class);
            // 获取用户信息
            UserInfo userInfo = payload.getUserInfo();
            // 验证token是否在黑名单中
            Boolean bol = redisTemplate.hasKey(payload.getId());
            if (bol != null && bol) {
                // 存在redis，说明token已经失效
                throw new LyException(ExceptionEnum.UNAUTHORIZED);
            }
            /*
             * 刷新用户登录状态
             */
            // 1. 先获取过期时间
            Date expiration = payload.getExpiration();
            // 2. 判断过期时间是否小于十分钟  minusMinutes(): 返回该日期时间的副本减去指定的分钟数。
            DateTime dateTime = new DateTime(expiration).minusMinutes(properties.getUser().getRefreshTime());
            // 判断是否在过期时间之前
            if (dateTime.isBeforeNow()) {
                // 重新生成token
                String newToken = JwtUtils.generateTokenExpireInMinutes(userInfo, properties.getPrivateKey(),
                        properties.getUser().getExpire());
                // 重新写入cookie
                CookieUtils.newCookieBuilder().name(properties.getUser().getCookieName())
                        .value(newToken)
                        .domain(properties.getUser().getCookieDomain())
                        .httpOnly(true).response(response).build();
            }
            // 返回用户信息
            return userInfo;
        } catch (Exception e) {
            // token无效，非法访问
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
    }

    public void logout(HttpServletRequest request, HttpServletResponse response) {
        // 获取cookie的名字
        String cookieName = properties.getUser().getCookieName();
        // 获取token
        String token = CookieUtils.getCookieValue(request, cookieName);
        // 解析、校验token
        Payload<Object> payload = null;
        try {
            payload = JwtUtils.getInfoFromToken(token, properties.getPublicKey());
        } catch (Exception e) {
            // token无效 无须后续操作
            return;
        }
        // 获取token剩余的时间
        long remainTime = payload.getExpiration().getTime() - System.currentTimeMillis();
        if (remainTime > 3000) {
            // 获取token的id存入redis，有效期是token剩余的时间
            String tokenId = payload.getId();
            redisTemplate.opsForValue().set(tokenId, "1", remainTime, TimeUnit.MILLISECONDS);
        }
        // 删除cookie
        CookieUtils.deleteCookie(cookieName, properties.getUser().getCookieDomain(), response);
    }

    public String authorization(Long id, String secret) {
        // 校验id
        ApplicationInfo applicationInfo = infoMapper.selectByPrimaryKey(id);
        if (applicationInfo == null) {
            // id都不存在，抛出异常
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
        // 校验secret
        if (!passwordEncoder.matches(secret, applicationInfo.getSecret())) {
            // 密钥不正确
            throw new LyException(ExceptionEnum.UNAUTHORIZED);
        }
        // 查询服务的权限信息
        List<Long> targetIdList = infoMapper.queryTargetIdList(id);
        // 封装一个AppInfo载荷
        AppInfo appInfo = new AppInfo(id,applicationInfo.getServiceName(),targetIdList);
        // 生成jwt并返回
        return JwtUtils.generateTokenExpireInMinutes(appInfo,
                properties.getPrivateKey(), properties.getApp().getExpire());
    }
}
